/**
 *  file_new()
 *  vytvori novy soubor v seznamu souboru
 *  IN:   <szFilename>  - nazev souboru ("new.txt", "/home/new.txt", ...)
 *  OUT:  [int] result
 */
int file_new(char far *filename) {
  int i           = 0;
  int lastCluster = 0;
  int freeCluster = 0;
  int fileCluster = 0;
  int page        = 0;
  int lpos        = 0;
  int result      = FNEW_OK;
  char far *realFilename      = mem_alloc(32, KERNEL_SEGMENT);      /* alokujeme si misto, kde bude nazev souboru velkymi pismeny */
  char far *buffer            = mem_alloc(0x220, KERNEL_SEGMENT);   /* alokujeme 512 bajtu pro sektor + pripadne 32 bajtu pro novy soubor */
  DirectoryEntry far *sfile   = mem_alloc(sizeof(DirectoryEntry), KERNEL_SEGMENT);
  FatBackupData  far *fbd     = fat_dirs_begin(&filename);

  /* vyparsujeme slozku a nazev souboru */
  fat_get_filename(filename, realFilename);

  /* zjistime, zda-li soubor/slozka jiz neexistuje */
  while((i = find_file(sfile, i)) != END_OF_DIR) {
    if(!strcmp((char far *)(_actual_lsegment + (int)(sfile->realname)), filename)) {
      result = FNEW_FAILED;
      goto fnew_end;
    }
  }

  /* vratime ukazatel na soubor na 0, a zjistime nejblizsi mozne volne
    misto (i po vymazanych souborech) pro nasi novou slozku             */
  i=0;
  while((i = find_file_care(sfile, i, (int far *)&page, (int far *)&lpos, true)) != END_OF_DIR);
  if(_dir_actual->c_start == DIR_ROOT) {                            /* pokud zapisujeme do "/" */
    fileCluster = fat_get_free_cluster();

    floppyread((_dir_actual->pos+page), 1, buffer);
    fat_entry_assign(buffer+(lpos*32), realFilename, F_ARCHIVE);    /* vytvorime si v pameti soubor */

    *((unsigned int far *)(buffer+(lpos*32)+0x1A)) = fileCluster;
    fat_write_entry(fileCluster, FAT_LASTCLUSTER);

    floppywrite((_dir_actual->pos+page), 1, buffer);
    fat_save();
  } else {
    if(!lpos) {                                                     /* pokud zapisujeme novy blok */
      lastCluster = fat_get_last_cluster_of_actual_dir();           /* zjistime posledni cluster nasi slozky */
      freeCluster = fat_get_free_cluster();                         /* zjistime nejblizsi volny cluster ve FAT */

      fat_write_entry(lastCluster, freeCluster);                    /* zapiseme pokracovani slozky do FAT */
      fat_write_entry(freeCluster, FAT_LASTCLUSTER);                /* ukoncime zaznam */

      floppyread((_fat->data_start_sector + (freeCluster - 2)),
                       1, buffer);                                  /* nacteme pozadovany sektor */
      fat_entry_assign(buffer, realFilename, F_ARCHIVE);            /* vytvorime si v pameti soubor */

      fileCluster = fat_get_free_cluster();                         /* zjistime nejblizsi volny cluster ve FAT */

      *((unsigned int far *)(buffer+0x1A)) = fileCluster;
      fat_write_entry(fileCluster, FAT_LASTCLUSTER);

      *(buffer+32) = '\0';                                          /* pro uzavreni slozky */
      floppywrite((_fat->data_start_sector + (freeCluster - 2)),
                        1, buffer);                                 /* zapiseme upraveny sektor */
      fat_save();                                                   /* ulozime si FAT */
    } else {                                                        /* !- nezapisujeme novy blok */
      lastCluster = fat_get_last_cluster_of_actual_dir();           /* zjistime posledni cluster nasi slozky */

      floppyread((_fat->data_start_sector + (lastCluster - 2)),
                       1, buffer);                                  /* nacteme pozadovany sektor */
      fat_entry_assign(buffer+(lpos*32), realFilename, F_ARCHIVE);  /* vytvorime si v pameti soubor */

      fileCluster = fat_get_free_cluster();                         /* zjistime nejblizsi volny cluster ve FAT */
      *((unsigned int far *)(buffer+(lpos*32)+0x1A)) = fileCluster;
      fat_write_entry(fileCluster, FAT_LASTCLUSTER);

      *(buffer+(lpos*32)+32) = '\0';                                /* pro uzavreni nazvu slozky */
      floppywrite((_fat->data_start_sector + (lastCluster - 2)),
                        1, buffer);                                 /* zapiseme upraveny sektor */
      fat_save();                                                   /* ulozime si FAT */
    }
  }

  /* uvolnime buffery */
  fnew_end:

  fat_dirs_end(fbd);
  mem_free(buffer);
  mem_free(realFilename);
  mem_free(sfile);
  return result;
}

/**
 *  file_open_care()
 *  otevre soubor, pokud je <dirs_too> [true], chova se k nalezene
 *  slozce jako k souboru, ktery se da klasicky cist
 */
FILE far *file_open_care(char far *filename, bool dirs_too) {
  int                i = 0, y = 0;                  /* pro cyklus */
  unsigned int       cluster  = 0;                  /* cluster */
  unsigned int       page     = 0;                  /* pro zjisteni stranky */
  unsigned int       c_dir    = 0;                  /* por zjisteni clusteru slozky */
  FILE far *result            = NULL;               /* vysledna promenna */
  DirectoryEntry far *sfile   = mem_alloc(sizeof(DirectoryEntry), KERNEL_SEGMENT);
  FatBackupData  far *fbd     = fat_dirs_begin(&filename);

  while((i = find_file(sfile, i)) != END_OF_DIR) {  /* najdeme soubor */
    if(!strcmp(sfile->realname, filename)) {        /* pokud jsme nasli soubor */
      if(sfile->attr == F_ARCHIVE
                    || dirs_too == true) {          /* zkontrolujeme, zda to opravdu je soubor, nebo jestli
                                                       mame povolene otevirat i slozky */
        cluster = sfile->ClstrNo;                   /* definujeme strukturu souboru */

        result              = mem_alloc(sizeof(FILE), _actual_segment);
        strcpy(result->filename, sfile->realname);
        result->buffer      = mem_alloc(1024, _actual_segment);  /* do segmentu _aplikace_ alokujeme misto pro dva sektory */
        result->pos         = 0;
        result->c_start     = cluster;
        result->c_actual    = cluster;
        result->size        = sfile->filesize;
        result->abs_pos     = 0;

        /* do bufferu zapiseme prvni segment */
        floppyread((_fat->data_start_sector + (result->c_actual - 2)), 1, result->buffer);

        /* zjistime pozici souboru ve slozce */
        c_dir = _dir_actual->c_start;
        page  = ((i-1)/16);

        if(_dir_actual->c_start != DIR_ROOT) {      /* pokud nejsme v "/" */
          for(y = 0; y < page; y++)                 /* prepocitame pozici ve strukture slozky podle pozice */
            fat_get_next_cluster(&c_dir);
        } else {
          c_dir += page;                            /* dosadime do pozice ve strukture slozky */
        }

        /* dosadime */
        result->dir_cluster = c_dir;
        result->dir_pos     = ((i-1)%16);
        result->sfile       = (DirectoryEntry *)sfile;

        break;
      }
    }
  }

  /* presuneme se do zalohovane slozky,
     uvolnime buffery a vratime pointer
     na strukturu souboru */
  fat_dirs_end(fbd);

  /* pokud jsme neotevreli soubor uspesne, muzeme dealokovat tuto promennou */
  if(result == NULL)
    mem_free(sfile);

  return result;
}

/**
 *  file_open()
 *  otevre soubor, vstup je nazev souboru, vystup je pointer
 *  na strukturu souboru nebo [NULL]
 *  IN:   <filename>  - nazev souboru k otevreni
 *        <mode>      - mod otevreni
 *  OUT:  <FILE *>    - pointer na strukturu
 */
FILE far *file_open(char far *filename, char far *mode) {
  /* TODO: dodelat mody */
  mode = mode;
  return file_open_care(filename, false);
}

/**
 *  file_dir_open()
 *  otevre zaznam nezavisle na tom, jestli je to soubor, slozka...
 *  vstup je nazev zaznamu, vystup je pointer
 *  na strukturu souboru nebo [NULL]
 *  IN:   <filename> - nazev souboru k otevreni
 *  OUT:  <FILE *>   - pointer na strukturu souboru
 */
FILE far *file_dir_open(char far *filename) {
  return file_open_care(filename, true);
}

/**
 *  file_seek()
 *  v souboru <stream> najede ukazatelem na <seek>
 *  pokud je overflow = true, soubor muze "zajet" i za aktualni konec souboru
 *  IN:   <seek>    - absolutni pozice v souboru, na kterou se nastavi ukazatel
 *        <stream>  - ukazatel na strukturu souboru
 *        <whence> -  odkud se bude seekovat
 *  OUT:  [int] navratove hodnoty:  0 - v poradku
 *                                  1 - chyba (konec souboru, chybny klastr...)
 */
int file_seek(FILE far *stream, int seek, int whence) {
  unsigned int cluster, last_cluster;
  int i;

  /* -=- SEEK_SET -=- */
  if(whence == SEEK_SET) {
    if(seek > stream->size)                       /* pokud chceme seekovat tam, kam nemame :) */
      return FSEEK_BREAK;

    stream->abs_pos = 0;
    stream->pos     = 0;
    cluster         = stream->c_start;            /* pozice bude absolutni, od zacatku souboru */
    fat_get_next_cluster(&cluster);               /* zjistime pristi cluster */
    if(fat_end(stream->c_actual))
      goto lret;                                  /* pokud konci soubor, nebo je poskozeny, ukonci cteni */

    for(i=0; i<seek; i++) {                       /* dokud nedojedeme na pozadovanou pozici */
      if(end_of_file(stream)) goto lret;          /* pokud by nahodou byl konec souboru, ukoncime cyklus */
      stream->abs_pos++;
      stream->pos++;                              /* inkrementujeme pozici */
      if(!(stream->pos%512)) {                    /* pokud je pozice 512, nacteme dalsi cluster */
        stream->pos = 0;                          /* vynulujeme pozici */
        stream->c_actual = cluster;               /* aktualizujeme aktualni klastr */
        fat_get_next_cluster(&cluster);
        if(fat_end(stream->c_actual))
          return FSEEK_BREAK;                     /* pokud konci soubor, nebo je poskozeny, ukonci cteni */
      }
    }
  }

  /* -=- SEEK_CUR -=- */
  if(whence == SEEK_CUR) {
    if((seek + stream->abs_pos) > stream->size)   /* pokud chceme seekovat tam, kam nemame :) */
      return FSEEK_BREAK;

    cluster = stream->c_actual;                   /* pozice bude aktualni */
    fat_get_next_cluster(&cluster);               /* zjistime pristi cluster */
    if(fat_end(stream->c_actual))
      goto lret;                                  /* pokud konci soubor, nebo je poskozeny, ukonci cteni */

    for(i=0; i<seek; i++) {                       /* dokud nedojedeme na pozadovanou pozici */
      if(end_of_file(stream)) goto lret;          /* pokud by nahodou byl konec souboru, ukoncime cyklus */
      stream->abs_pos++;
      stream->pos++;                              /* inkrementujeme pozici */
      if(!(stream->pos%512)) {                    /* pokud je pozice 512, nacteme dalsi cluster */
        stream->pos = 0;                          /* vynulujeme pozici */
        stream->c_actual = cluster;               /* aktualizujeme aktualni klastr */
        fat_get_next_cluster(&cluster);
        if(fat_end(stream->c_actual))
          return FSEEK_BREAK;                     /* pokud konci soubor, nebo je poskozeny, ukonci cteni */
      }
    }
  }

  /* -=- SEEK_END -=- */
  if(whence == SEEK_END) {
    if(seek > stream->size)                       /* pokud chceme seekovat tam, kam nemame :) */
      return FSEEK_BREAK;

    stream->abs_pos = 0;
    stream->pos     = 0;
    cluster         = stream->c_start;            /* pozice bude absolutni, od zacatku souboru */
    fat_get_next_cluster(&cluster);               /* zjistime pristi cluster */
    if(fat_end(stream->c_actual))
      goto lret;                                  /* pokud konci soubor, nebo je poskozeny, ukonci cteni */

    for(i=0; i<(stream->size - seek); i++) {      /* dokud nedojedeme na pozadovanou pozici */
      if(end_of_file(stream)) goto lret;          /* pokud by nahodou byl konec souboru, ukoncime cyklus */
      stream->abs_pos++;
      stream->pos++;                              /* inkrementujeme pozici */
      if(!(stream->pos%512)) {                    /* pokud je pozice 512, nacteme dalsi cluster */
        stream->pos = 0;                          /* vynulujeme pozici */
        stream->c_actual = cluster;               /* aktualizujeme aktualni klastr */
        fat_get_next_cluster(&cluster);
        if(fat_end(stream->c_actual))
          return FSEEK_BREAK;                     /* pokud konci soubor, nebo je poskozeny, ukonci cteni */
      }
    }
  }

  return FSEEK_OK;

  lret:
  return FSEEK_BREAK;
}

/**
 *  file_tell()
 *  vrati aktualni pozici ukazatele v souboru
 *  IN:   <stream>  - ukazatel na strukturu souboru
 *  OUT:  [int]     - pozice v souboru
 */
int file_tell(FILE far *stream) {
  return stream->abs_pos;
}

/**
 *  end_of_file()
 *  zjisti, zda-li je konec souboru
 *  IN:   <stream>  - ukazatel na strukturu souboru
 *  OUT:  [bool] true(ano), false(ne)
 */
bool end_of_file(FILE far *stream) {
  if(file_tell(stream) >= file_size(stream))      /* pokud je aktualni pozice rovna
                                                     (nebo vetsi nez) delka souboru */
    return true;
  else
    return false;
}

/**
 *  file_size()
 *  zjisti velikost souboru <stream> a vrati ho
 *  IN:   <stream>  - ukazatel na strukturu souboru
 *  OUT:  [int]     - velikost souboru
 */
int file_size(FILE far *stream) {
  return stream->size;
}

/**
 *  file_read()
 *  precte <size> bajtu a dosadi je do bufferu <buff> ze souboru <stream>
 *  IN:   <buffer>  - pointer na buffer, kam se ulozi data
 *        <size>    - velikost nacteneho bloku dat
 *        <stream>  - ukazatel na strukturu souboru
 *  OUT:  [void]
 */
void far *file_read(char far *buffer, int size, FILE far *stream) {
  unsigned int cluster       = stream->c_actual;        /* budeme cist od aktualni pozice */
  unsigned int i             = 0;                       /* cyklus */

  for(i=0; i<size; i++)                                 /* vycistime buffer */
    buffer[i] = '\0';

  if (fat_end(stream->c_actual)) {
    buffer = NULL;                                      /* jestli chceme cist neco co neni */
    goto lret;
  }

  fat_get_next_cluster(&cluster);

  if(fat_end(stream->c_actual))
    goto lret;                                          /* pokud konci soubor, nebo je poskozeny, ukonci cteni */

  for(i = 0; i<size; i++) {                             /* dokud neprecteme pozadovanych <i> dat */
    if(end_of_file(stream)) break;                      /* pokud je konec souboru, ukoncime cteni */
    stream->abs_pos++;
    buffer[i] = stream->buffer[stream->pos++];          /* znak po znaku nateme data do bufferu */
    if(stream->pos%512 == 0) {                          /* pokud je pozice 512, nacteme dalsi klastr */
      stream->pos = 0;                                  /* vynulujeme pozici */
      floppyread((_fat->data_start_sector + (cluster - 2)),
                                   1, stream->buffer);  /* precteme klastr, dosadime do <tempMem> */
      stream->c_actual = cluster;                       /* aktualizujeme aktualni klastr */
      fat_get_next_cluster(&cluster);                   /* zjistime pristi klastr */
      if(fat_end(stream->c_actual))
        break;                                          /* pokud konci soubor, nebo je poskozeny, ukonci cteni */

    }                                                   /* a pokracujeme v cyklu */
  }

  lret:                                                 /* odpustte mensi skromnost "goto" :),
                                                           jde preci jen o jmp :) */
  return buffer;
}

/**
 *  file_write()
 *  zapise <size> bajtu a z bufferu <buff> do souboru <stream>
 *  IN:   <buffer>  - pointer na buffer, za ktereho se zapisi data
 *        <size>    - velikost zapisovaneho bloku dat
 *        <stream>  - ukazatel na strukturu souboru
 *  OUT:  [void]
 */
void file_write(char far *buffer, int size, FILE far *stream) {
  unsigned int cluster         = stream->c_actual;      /* budeme cist od aktualni pozice */
  unsigned int lastCluster     = cluster;
  unsigned int lastRealCluster = cluster;
  unsigned int freeCluster     = 0;
  int          i,y             = 0;                     /* cyklus */

  fat_get_next_cluster(&cluster);

  if(fat_end(stream->c_actual)) {                       /* pokud konci soubor, nebo je poskozeny, ukonci cteni */
    freeCluster = fat_get_free_cluster();               /* zjistime nejblizsi volny cluster ve FAT */
    fat_write_entry(stream->c_actual, freeCluster);     /* zapiseme pokracovani slozky do FAT */
    fat_write_entry(freeCluster, FAT_LASTCLUSTER);      /* ukoncime zaznam */
    stream->c_actual = freeCluster;

    fat_save();
  }

  for(i=0; i<size; i++) {                               /* dokud neprecteme pozadovanych <i> dat */
    stream->abs_pos++;
    stream->buffer[stream->pos++] = buffer[i];          /* znak po znaku nateme data do bufferu */
    if(stream->pos%512 == 0) {                          /* pokud je pozice 512, nacteme dalsi klastr */
      stream->pos = 0;                                  /* vynulujeme pozici */
      floppywrite((_fat->data_start_sector + (lastCluster - 2)), 1, stream->buffer);
      floppyread(( _fat->data_start_sector + (cluster - 2)),
                                   1, stream->buffer);  /* precteme klastr, dosadime do <tempMem> */
      lastCluster = cluster;
      stream->c_actual = cluster;                       /* aktualizujeme aktualni klastr */

      if(fat_end(stream->c_actual)) {
        freeCluster = fat_get_free_cluster();           /* zjistime nejblizsi volny cluster ve FAT */
        fat_write_entry(lastRealCluster, freeCluster);  /* zapiseme pokracovani slozky do FAT */
        fat_write_entry(freeCluster, FAT_LASTCLUSTER);  /* ukoncime zaznam */

        lastCluster      = freeCluster;
        stream->c_actual = lastCluster;                 /* v dalsich cyklech zapisujeme do noveho clusteru */

        fat_save();
      }
      lastRealCluster = freeCluster;
    }                                                   /* a pokracujeme v cyklu */
  }

  if(stream->pos%512 != 0)                              /* pokud jsme predtim nezapisovali na disk */
    floppywrite((_fat->data_start_sector + (lastCluster - 2)),
                      1, stream->buffer);               /* zapiseme nyni */

  return;
}

/**
 *  file_close()
 *  uzavre soubor <stream>
 *  IN:   <stream>  - ukazatel na strukturu souboru
 *  OUT:  [void]
 */
void file_close(FILE far *stream) {
  DirectoryEntry far *t_sfile = stream->sfile;

  t_sfile->filesize = (unsigned int)(stream->abs_pos > stream->size ? stream->abs_pos : stream->size);
  fat_edit_entry_by_cluster(t_sfile, stream->dir_cluster, stream->dir_pos);
  mem_free(stream->buffer);
  mem_free(t_sfile);
  mem_free(stream);
  return;
}

/**
 *  file_remove()
 *  vymaze soubor <filename>
 *  IN:    <filename> - nazev souboru, je brana i absolutni cesta
 *  OUT:   [int] result
 */
int file_remove(char far *filename) {
  int          i              = 0;
  int          page           = 0;
  int          lpos           = 0;
  int          result         = REMOVE_FAILED;
  unsigned int firstCluster   = 0;
  unsigned int cluster        = 0;
  char far *realFilename      = mem_alloc(32, KERNEL_SEGMENT);      /* alokujeme si misto, kde bude nazev souboru velkymi pismeny */
  char far *buffer            = mem_alloc(0x220, KERNEL_SEGMENT);   /* alokujeme 512 bajtu pro sektor + 32 bajtu pro pripadny novy soubor */
  DirectoryEntry far *sfile   = mem_alloc(sizeof(DirectoryEntry), KERNEL_SEGMENT);
  FatBackupData  far *fbd     = fat_dirs_begin(&filename);

  /* vyparsujeme slozku a nazev souboru */
  fat_get_filename(filename, realFilename);

  while((i = find_file_care(sfile, i, &page, &lpos, false)) != END_OF_DIR) {
    if(!strcmp(sfile->realname, filename)) {
      if(_dir_actual->c_start == DIR_ROOT) {                        /* pokud zapisujeme do "/" */
        floppyread((_dir_actual->pos+page), 1, buffer);
        *(buffer+((lpos-1)*32)) = DELETED;                          /* pozmenime v RAM strukturu */
        firstCluster = *((unsigned int far *)(buffer+((lpos-1)*32)+0x1A));
        fat_write_entry(firstCluster, FAT_AVAILABLE);
        while(!fat_end(fat_get_next_cluster(&firstCluster)))        /* vymazeme clustery ve FAT */
          fat_write_entry(firstCluster, FAT_AVAILABLE);
        floppywrite((_dir_actual->pos+page), 1, buffer);
        fat_save();
      } else {
        cluster = _dir_actual->c_start;
        for(i=0;i<page;i++)
          fat_get_next_cluster(&cluster);
        floppyread((_fat->data_start_sector + (cluster - 2)), 1, buffer);
        *(buffer+((lpos-1)*32)) = DELETED;                          /* pozmenime v RAM strukturu */
        firstCluster = *((unsigned int far *)(buffer+((lpos-1)*32)+0x1A));
        fat_write_entry(firstCluster, FAT_AVAILABLE);
        while(!fat_end(fat_get_next_cluster(&firstCluster)))        /* vymazeme clustery ve FAT */
          fat_write_entry(firstCluster, FAT_AVAILABLE);
        floppywrite((_fat->data_start_sector + (cluster - 2)), 1, buffer);
        fat_save();
      }

      result = REMOVE_OK;
      break;
    }
  }

  /* uvolnime buffery */
  fat_dirs_end(fbd);
  mem_free(buffer);
  mem_free(realFilename);
  mem_free(sfile);
  return result;
}

/**
 *  file_rename()
 *  prejmenuje soubor <oldname> na <newname>
 *  IN:   <oldname>   - aktualni nazev
 *        <newname>   - novy nazev
 *  OUT:  [int] result
 */
int file_rename(const char far *oldnamec, const char far *newname) {
  int result                = RENAME_OK;
  FILE far *stream;
  char far *fatNewFileName  = mem_alloc(13, KERNEL_SEGMENT);
  char far *oldname         = mem_alloc(strlen(oldnamec)+1, KERNEL_SEGMENT);
  FatBackupData far *fbd;

  strcpy(oldname, oldnamec);

  fbd = fat_dirs_begin(&oldname);

  if(pos(newname, '/') != POS_NOT_FOUND)
    newname += l_pos(newname, '/')+1;

  stream = file_dir_open(oldname);
  if(stream == NULL)
    result = RENAME_FAILED;

  fat_get_filename(newname, fatNewFileName);
  memcpy(stream->sfile, fatNewFileName, 11);
  file_close(stream);

  fat_dirs_end(fbd);
  mem_free(fatNewFileName);
  mem_free(oldname);
  return result;
}
